/**
 *
 */
package com.elcom.emocbc.cbm;

import com.elcom.emocbc.db.CellBroadcastDAO;
import com.elcom.emocbc.ipc.IPCCellBroadcastClientTrap;
import com.elcom.emocbc.util.ConfigurationManager;
import com.elcom.emocbc.util.GeneratorSeqIPC;
import com.elcom.livescreen.common.ipc.message.cbcmessage.BROADCAST_SUBMIT_ACK;
import com.elcom.livescreen.common.ipc.message.cbcmessage.BROADCAST_SUBMIT_REQ;
import com.elcom.livescreen.common.ipc.transport.pending_response.InvalidResponseException;
import com.elcom.livescreen.common.ipc.transport.pending_response.ResponseTimeoutException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.xml.DOMConfigurator;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;

/**
 * @author tiamo114
 *
 */
public class CellBroadcastService {

    private static final Log LOG = LogFactory.getLog(CellBroadcastService.class);
    public static final int NORMAL = 0;
    public static final int PROCESSING = 5;
    public static final int SIZE_1_PART = 88;
    private MonitorThread monitorThread = null;
    private BroadcastThread broadcastThread = null;
    private BroadcastUnit[] broadcastUnits;
    private SynchronousQueue<BroadcastDTO> q = null;
    private Scheduler sched = null;
    private static IPCCellBroadcastClientTrap ipcClient = null;
    //Config Parameters
    public static int numberBroadCastUnit;
    public static int maxTime;
    public static int minTime;
    public static long timeout;
    public static long interval;
    public static int cycleGetBroadcast;

    public CellBroadcastService(String ipcName) throws Throwable {
        DOMConfigurator.configureAndWatch(System.getProperty("user.home") + File.separator + "config/cellbroadcast" + File.separator + "log4j.xml");
        reLoadConfig();

        // First we must get a reference to a scheduler
        SchedulerFactory sf = new StdSchedulerFactory();
        sched = sf.getScheduler();
        LOG.info("------- Initialization Complete -----------");

        sched.start();
        LOG.info("------- Started Scheduler -----------------");

        ipcClient = new IPCCellBroadcastClientTrap("CellBroadcast", "CellBroadcast", System.getProperty("user.home") + File.separator + "config/cellbroadcast" + File.separator + "cellbroadcast.cfg");
        ipcClient.start();

        q = new SynchronousQueue<BroadcastDTO>();
        broadcastUnits = new BroadcastUnit[numberBroadCastUnit];
        for (int i = 0; i < broadcastUnits.length; i++) {
            broadcastUnits[i] = new BroadcastUnit("BroadcastUnit" + i, q, sched);
            broadcastUnits[i].execute();
        }

        broadcastThread = new BroadcastThread("BroadcastThread", q);
        broadcastThread.execute();

        monitorThread = new MonitorThread("MonitorThread", ipcName);
        monitorThread.execute();
    }

    public static void reLoadConfig() {
        ConfigurationManager.reloadConfiguration();
        numberBroadCastUnit = ConfigurationManager.getIntProp("cell_broadcast", "broadcast.unit.number", 2);
        maxTime = ConfigurationManager.getIntProp("cell_broadcast", "maxtime_s", 120);
        minTime = ConfigurationManager.getIntProp("cell_broadcast", "mintime_s", 120);
        timeout = ConfigurationManager.getIntProp("cell_broadcast", "timeout_s", 3) * 1000;
        interval = ConfigurationManager.getIntProp("cell_broadcast", "interval_s", 5) * 1000;
        cycleGetBroadcast = ConfigurationManager.getIntProp("cell_broadcast", "cell_broadcast.cycle_m", 1);
        CellBroadcastDAO.SQL_GET_LIST_BROADCAST = ConfigurationManager.getStrProp("cell_broadcast", "SQL_LIST_BROADCAST", "SELECT a.broadcasttime_id, a.zone_id, a.createdby, a.createddate, d.program_id, b.topic_id, c.zonename, a.starttime, a.program_indexs, a.cp_topic_id FROM broadcasttime a, cp_topic b, zones c, programs d where a.zone_id=c.zone_id and a.status=0 and d.status=1 and a.cp_topic_id=b.cp_topic_id and (a.cp_topic_id=d.cps_topic_id and a.program_indexs=d.indexs) and to_date(TO_CHAR(SYSDATE,'dd/MM/yyyy')||' '||a.starttime,'dd/MM/yyyy hh24:mi') between SYSDATE and SYSDATE+(10/1440)");
    }

    /**
     * @param args
     * @throws Throwable
     */
    public static void main(String[] args) throws Throwable {
        new CellBroadcastService(args[0]);
    }
    static String HEX_STRING = "0123456789ABCDEF";

    public static String convertBinary2Hexadecimal(byte[] binary) {
        StringBuilder buf = new StringBuilder();
        int block;

        for (int i = 0; i < binary.length; i++) {
            block = binary[i] & 0xFF;
            buf.append(HEX_STRING.charAt(block >> 4));
            buf.append(HEX_STRING.charAt(binary[i] & 0x0F));
            if (i < binary.length - 1) {
                buf.append(',');
            }
        }

        return buf.toString();
    }

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    public static String stringToHex(String str) {
        char[] chars = str.toCharArray();
        StringBuilder strBuffer = new StringBuilder();
        for (int i = 0; i < chars.length; i++) {
            strBuffer.append(IntegerToHex((int) chars[i]));
        }
        return strBuffer.toString();
    }

    private static String IntegerToHex(int i) {
        if (i < 16) {
            return "0" + Integer.toHexString(i);
        }

        return Integer.toHexString(i);
    }

    private static String getMostContent(String addresses[]) {
        int howMany;
        int mostTimes = 1;
        String mostCommon = null;
        for (int i = 0; i < addresses.length; i++) {
            if (addresses[i] == null || addresses[i].length() == 0) {
                continue;
            }
            howMany = 0;
            for (int j = 0; j < addresses.length; j++) {
                if (addresses[i].equals(addresses[j])) {
                    howMany++;
                }
            }

            if (howMany > mostTimes) {
                mostTimes = howMany;
                mostCommon = addresses[i];
            }
        }

        return mostCommon;
    }

    private static int getMostNumber(int addresses[]) {
        int howMany;
        int mostTimes = 1;
        int mostCommon = 0;
        for (int i = 0; i < addresses.length; i++) {
            if (addresses[i] == 0) {
                continue;
            }
            howMany = 0;
            for (int j = 0; j < addresses.length; j++) {
                if (addresses[i] == (addresses[j])) {
                    howMany++;
                }
            }

            if (howMany > mostTimes) {
                mostTimes = howMany;
                mostCommon = addresses[i];
            }
        }

        return mostCommon;
    }

    public static BROADCAST_SUBMIT_ACK generateCBMStructFile(MenuItemDTO[] menu, String detail, int messageID, int topicID, int ind, String zone, String startTime, int broadcastId)
            throws IOException {
        int serialNumber = 0xB3A0;
        byte[] sum;
        int n = 0;
        int realSize = 0;
        int sizeM = 0;
        int sizeD;
        int lengthHide;
        String beginHex = "";
        String menuHex = "";
        String detailHex = "";

        int numberMenu = menu.length;

        int[] number = new int[menu.length];
        for (int i = 0; i < number.length; i++) {
            number[i] = menu[i].getShortcode();
        }

        String[] content = new String[menu.length];
        for (int i = 0; i < content.length; i++) {
            content[i] = menu[i].getContent();
            LOG.debug("content" + i + ": " + content[i]);
        }

        int defaultNumber = getMostNumber(number);
        String defaultContent = getMostContent(content);

        //for Test
//		defaultNumber = 0;
//		defaultContent = null;

        LOG.debug("Start broadcast for topicID: " + topicID + "---defaultNumber=" + defaultNumber + "---defaultContent=" + defaultContent);

        if (defaultNumber == 0 && defaultContent == null) {//OK DONE
            for (int i = 0; i < menu.length; i++) {
                if (menu[i].getType().startsWith("ZZ")) {
                    sizeM += 1;
                    menuHex += menu[i].getType();
                    menuHex += IntegerToHex(menu[i].getContent().length());
                } else {
                    sizeM += 4;
                    menuHex += menu[i].getType() + "81";
                    menuHex += toShortCodeString(menu[i].getShortcode());

                    String tmp = IntegerToHex(menu[i].getContent().length());
                    if (menu[i].getType().equals("B3")) {
                        tmp = tmp.replaceFirst("0", "8");
                    }

                    menuHex += tmp;
                }

                menuHex += stringToHex(menu[i].getContent());
                menuHex += IntegerToHex(menu[i].getDisplay().length()) + stringToHex(menu[i].getDisplay());

                sizeM += menu[i].getContent().length() + 1;
                sizeM += menu[i].getDisplay().length() + 1;
            }

            lengthHide = 12 + sizeM;

            sizeD = detail.length();

            for (int i = 1; i <= 5; i++) {
                int tmp = 21 + sizeD + 6 + sizeM - 8 + 9 * i;
                LOG.debug("Test bug: " + tmp + "--" + i * SIZE_1_PART);
                if (tmp <= i * SIZE_1_PART) {
                    n = i;
                    realSize = tmp;
                    sum = new byte[i * SIZE_1_PART];
                    break;
                }
            }

            // Init
            beginHex = IntegerToHex(serialNumber) + "1066F211" + IntegerToHex(messageID) + "" + 1 + "" + n + IntegerToHex(lengthHide) + "30"
                    + IntegerToHex(numberMenu + 1) + IntegerToHex(topicID)
                    + "0000F00511030101" + IntegerToHex(topicID);

            detailHex = "010200" + IntegerToHex(sizeD + 2) + "3E3E" + stringToHex(detail);

            int k = n * SIZE_1_PART - realSize;
            for (int i = 0; i < k; i++) {
                detailHex += "0D";
            }
        } else if (defaultNumber != 0 && defaultContent == null) {//OK DONE
            for (int i = 0; i < menu.length; i++) {
                if (menu[i].getType().equals("A3")) {
                    if (menu[i].getShortcode() == defaultNumber) {
                        menu[i].setType("A0");
                        sizeM += 1;
                        menuHex += menu[i].getType();
                    } else {
                        sizeM += 4;
                        menuHex += menu[i].getType() + "81";
                        menuHex += toShortCodeString(menu[i].getShortcode());
                    }
                    menuHex += IntegerToHex(menu[i].getContent().length());

                } else {
                    if (menu[i].getShortcode() == defaultNumber) {
                        menu[i].setType("B0");
                        sizeM += 1;
                        menuHex += menu[i].getType();
                    } else {
                        sizeM += 4;
                        menuHex += menu[i].getType() + "81";
                        menuHex += toShortCodeString(menu[i].getShortcode());
                    }

                    String tmp = IntegerToHex(menu[i].getContent().length());
                    if (menu[i].getType().equals("B3")) {
                        tmp = tmp.replaceFirst("0", "8");
                    }
                    menuHex += tmp;
                }

                menuHex += stringToHex(menu[i].getContent());
                menuHex += IntegerToHex(menu[i].getDisplay().length()) + stringToHex(menu[i].getDisplay());

                sizeM += menu[i].getContent().length() + 1;
                sizeM += menu[i].getDisplay().length() + 1;
            }

            lengthHide = 15 + sizeM;

            sizeD = detail.length();

            for (int i = 1; i <= 5; i++) {
                int tmp = 24 + sizeD + 6 + sizeM - 8 + 9 * i;
                if (tmp <= i * SIZE_1_PART) {
                    n = i;
                    realSize = tmp;
                    sum = new byte[i * SIZE_1_PART];
                    break;
                }
            }

            // Init
            beginHex = IntegerToHex(serialNumber) + "1066F211" + IntegerToHex(messageID) + "" + 1 + "" + n + IntegerToHex(lengthHide) + "30"
                    + IntegerToHex(numberMenu + 1) + IntegerToHex(topicID)
                    + "0381" + toShortCodeString(defaultNumber) + "00F00511030101" + IntegerToHex(topicID);

            detailHex = "010200" + IntegerToHex(sizeD + 2) + "3E3E" + stringToHex(detail);

            int k = n * SIZE_1_PART - realSize;
            for (int i = 0; i < k; i++) {
                detailHex += "0D";
            }
        } else if (defaultNumber == 0 && defaultContent != null) {
            for (int i = 0; i < menu.length; i++) {
                sizeM += 4;
                menuHex += menu[i].getType() + "81";
                menuHex += toShortCodeString(menu[i].getShortcode());

                if (menu[i].getContent().equals(defaultContent)) {
                    if (menu[i].getType().startsWith("A")) {
                        menuHex += "00";
                    } else {
                        menuHex += "80";
                    }
                    sizeM += 1;
                } else {
                    if (menu[i].getType().startsWith("A")) {
                        menuHex += IntegerToHex(menu[i].getContent().length());
                    } else {
                        String tmp = IntegerToHex(menu[i].getContent().length());
                        if (menu[i].getType().equals("B3")) {
                            tmp = tmp.replaceFirst("0", "8");
                        }
                        menuHex += tmp;
                    }

                    menuHex += stringToHex(menu[i].getContent());
                    sizeM += menu[i].getContent().length() + 1;
                }

                menuHex += IntegerToHex(menu[i].getDisplay().length()) + stringToHex(menu[i].getDisplay());
                sizeM += menu[i].getDisplay().length() + 1;
//				
//
//				menuHex += stringToHex(menu[i].getContent());
//				menuHex += IntegerToHex(menu[i].getDisplay().length()) + stringToHex(menu[i].getDisplay());
//
//				sizeM += menu[i].getContent().length() + 1;
//				sizeM += menu[i].getDisplay().length() + 1;
            }
            //ThanhTT- Them 23-10-2012
            defaultContent = stringToHex(defaultContent);
            //End- ThanhTT

            lengthHide = 12 + defaultContent.length() / 2 + sizeM;

            sizeD = detail.length();

            for (int i = 1; i <= 5; i++) {
                int tmp = 21 + defaultContent.length() / 2 + sizeD + 6 + sizeM - 8 + 9 * i;
                if (tmp <= i * SIZE_1_PART) {
                    n = i;
                    realSize = tmp;
                    sum = new byte[i * SIZE_1_PART];
                    break;
                }
            }

            // Init
            beginHex = IntegerToHex(serialNumber) + "1066F211" + IntegerToHex(messageID) + "" + 1 + "" + n + IntegerToHex(lengthHide) + "30"
                    + IntegerToHex(numberMenu + 1) + IntegerToHex(topicID)
                    + "00" + IntegerToHex(defaultContent.length() / 2) + defaultContent + "F00511030101" + IntegerToHex(topicID);

            detailHex = "010200" + IntegerToHex(sizeD + 2) + "3E3E" + stringToHex(detail);

            int k = n * SIZE_1_PART - realSize;
            for (int i = 0; i < k; i++) {
                detailHex += "0D";
            }
        } else if (defaultNumber != 0 && defaultContent != null) {
            for (int i = 0; i < menu.length; i++) {
                if (menu[i].getType().equals("A3")) {
                    if (menu[i].getShortcode() == defaultNumber) {
                        menu[i].setType("A0");
                        sizeM += 1;
                        menuHex += menu[i].getType();
                    } else {
                        sizeM += 4;
                        menuHex += menu[i].getType() + "81";
                        menuHex += toShortCodeString(menu[i].getShortcode());
                    }
                } else {
                    if (menu[i].getShortcode() == defaultNumber) {
                        menu[i].setType("B0");
                        sizeM += 1;
                        menuHex += menu[i].getType();
                    } else {
                        sizeM += 4;
                        menuHex += menu[i].getType() + "81";
                        menuHex += toShortCodeString(menu[i].getShortcode());
                    }
                }


                if (menu[i].getContent().equals(defaultContent)) {
                    if (menu[i].getType().startsWith("A")) {
                        menuHex += "00";
                    } else {
                        menuHex += "80";
                    }
                    sizeM += 1;
                } else {
                    if (menu[i].getType().startsWith("A")) {
                        menuHex += IntegerToHex(menu[i].getContent().length());
                    } else {
                        String tmp = IntegerToHex(menu[i].getContent().length());
                        if (menu[i].getType().equals("B3")) {
                            tmp = tmp.replaceFirst("0", "8");
                        }
                        menuHex += tmp;
                    }

                    menuHex += stringToHex(menu[i].getContent());
                    sizeM += menu[i].getContent().length() + 1;
                }

                menuHex += IntegerToHex(menu[i].getDisplay().length()) + stringToHex(menu[i].getDisplay());
                sizeM += menu[i].getDisplay().length() + 1;
            }
            //ThanhTT- Them 23-10-2012
            defaultContent = stringToHex(defaultContent);
            //End- ThanhTT

            lengthHide = 15 + defaultContent.length() / 2 + sizeM;

            sizeD = detail.length();

            for (int i = 1; i <= 5; i++) {
                int tmp = 24 + defaultContent.length() / 2 + sizeD + 6 + sizeM - 8 + 9 * i;
                if (tmp <= i * SIZE_1_PART) {
                    n = i;
                    realSize = tmp;
                    sum = new byte[i * SIZE_1_PART];
                    break;
                }
            }

            // Init
            beginHex = IntegerToHex(serialNumber) + "1066F211" + IntegerToHex(messageID) + "" + 1 + "" + n + IntegerToHex(lengthHide) + "30"
                    + IntegerToHex(numberMenu + 1) + IntegerToHex(topicID)
                    + "0381" + toShortCodeString(defaultNumber) + IntegerToHex(defaultContent.length() / 2) + defaultContent + "F00511030101" + IntegerToHex(topicID);

            detailHex = "010200" + IntegerToHex(sizeD + 2) + "3E3E" + stringToHex(detail);

            int k = n * SIZE_1_PART - realSize;
            for (int i = 0; i < k; i++) {
                detailHex += "0D";
            }
        }

        LOG.debug("Test broadcast: " + beginHex + "---" + menuHex + "---" + detailHex);


        byte[] beginArray = hexStringToByteArray(beginHex);
        byte[] menuArray = hexStringToByteArray(menuHex);
        byte[] detailArray = hexStringToByteArray(detailHex);

        sum = new byte[beginArray.length + menuArray.length + detailArray.length];
        System.arraycopy(beginArray, 0, sum, 0, beginArray.length);
        System.arraycopy(menuArray, 0, sum, beginArray.length, menuArray.length);
        System.arraycopy(detailArray, 0, sum, beginArray.length + menuArray.length, detailArray.length);

        CellBroadcastDAO dao = new CellBroadcastDAO();
        int distanceTime = dao.calculteTimeDistance(startTime);
        if (distanceTime > CellBroadcastService.maxTime) {
            distanceTime = CellBroadcastService.maxTime;
        }

        if (distanceTime < CellBroadcastService.minTime) {
            distanceTime = CellBroadcastService.minTime;
        }
        LOG.debug("Test distanceTime: " + distanceTime + "---" + n + "---" + interval / 1000);

        int numberLoop;
        if (ConfigurationManager.getBooleanProp("cell_broadcast", "fixed_numberLoop", false)) {
            //gianglt: bỏ cách lặp cũ, số lần lặp sẽ đc config
            //tham số config là: numberLoop (giá trị mặc đinh = 1)
            numberLoop = ConfigurationManager.getIntProp("cell_broadcast", "numberLoop", 1);
            LOG.debug("Number of loops is fixed as " + numberLoop);
        } else {
            numberLoop = (int) (distanceTime / (n * ((int) (CellBroadcastService.interval / 1000))));
//            numberLoop = numberLoop - 1;//Giam di 1 lan de tranh goi len nhau
            LOG.debug("Number of loops is " + numberLoop);
        }
        
        BROADCAST_SUBMIT_ACK resp = new BROADCAST_SUBMIT_ACK();
        resp.setErrorCode(BROADCAST_SUBMIT_ACK.BROADCAST_RESULT.UNKNOWN.value());
        
        while (numberLoop > 0) {
            numberLoop--;
            int index = 0;
            Random rnd = new Random();
            int newIndex = rnd.nextInt(10000);
            for (int i = 0; i < n; i++) {
                byte[] out = new byte[88];
                if (i > 0) {
                    serialNumber = serialNumber + i * 16;
                    String start = IntegerToHex(serialNumber) + "1066F211" + IntegerToHex(messageID) + "" + (i + 1) + "" + n;
                    byte[] startArray = hexStringToByteArray(start);

                    System.arraycopy(startArray, 0, out, 0, startArray.length);
                    System.arraycopy(sum, index, out, 8, 87 - startArray.length);
                    index = index + (87 - startArray.length);
                } else {
                    System.arraycopy(sum, 0, out, 0, 87);
                    index = 87;
                }

                out[87] = (byte) 0xFF;

                byte[] out_file = new byte[82];
                System.arraycopy(out, 6, out_file, 0, 82);

                // Write to file
                String path = System.getProperty("user.home") + File.separator + "cbm";
                File f = new File(path);
                if (!f.exists()) {
                    f.mkdir();
                }

                FileOutputStream op = new FileOutputStream(new File(path + File.separator + "news" + newIndex + "_CBM" + i));
                op.write(out_file);
                op.flush();
                op.close();

                BROADCAST_SUBMIT_REQ req = new BROADCAST_SUBMIT_REQ();
                req.setSeqId(GeneratorSeqIPC.getNextSeq());
                req.setBroadcastAreaName(zone);
                req.setBroadcastText(convertBinary2Hexadecimal(out_file));

                try {
                    resp = (BROADCAST_SUBMIT_ACK) ipcClient.syncRequest(req, CellBroadcastService.timeout);
                } catch (ResponseTimeoutException e1) {
                    LOG.error("ResponseTimeoutException: ", e1);
                } catch (InvalidResponseException e1) {
                    LOG.error("InvalidResponseException: ", e1);
                } catch (Throwable e1) {
                    LOG.error("Exception: ", e1);
                }
                LOG.info("Broadcast result: " + resp.getErrorCode() + " - " + resp.getErrorDetail() + " - CBC TransactionId: " + resp.getCbcTransactionId());
                dao.insertCbcTransactionMap(broadcastId, resp.getCbcTransactionId());
                if (i < n - 1) {
                    try {
                        Thread.sleep(CellBroadcastService.interval);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }

        return resp;
    }

    private static String toShortCodeString(int defaultCode) {
        StringBuilder shortCode = new StringBuilder(String.valueOf(defaultCode));
        if (shortCode.length() % 2 == 1) {
            shortCode.append("F");
        }

        StringBuilder result = new StringBuilder();
        for (int i = 0; i < shortCode.length(); i += 2) {
            result.append(shortCode.charAt(i + 1)).append(shortCode.charAt(i));
        }

        return result.toString();
    }
}
